﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Freedom.Common.Collections
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public abstract class ObjectCollection<T> : ObjectCollectionBase<T>, IObjectCollection<T>
        where T : class, ICopiable<T>, IIdentifiable
    {
        // Events
        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        [field: NonSerialized]
        public event CollectionChangedEventHandler<T> CollectionChanged;



        #region Interface

        #region Base Implementation
        /// <summary>
        /// SynchRoot
        /// </summary>
        /// <value></value>
        object IObjectCollection<T>.SynchRoot
        {
            get { return SynchRoot; }
        }

        /// <summary>
        /// Adds the specified item. base emthod for batch procressing
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        CollectionChangedEventArgs<T> IObjectCollection<T>.AddItemBase(T item)
        {
            return AddItemBase(item);
        }

        /// <summary>
        /// Adds the specified item. base emthod for batch procressing
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected CollectionChangedEventArgs<T> AddItemBase(T item)
        {
            if (item == null) throw new ArgumentNullException("item");

            Items.Add(item);
            return new CollectionChangedEventArgs<T>(CollectionChangedAction.Add, item);
        }

        /// <summary>
        /// Adds the specified item. base emthod for batch procressing
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        CollectionChangedEventArgs<T> IObjectCollection<T>.InsertToTopItemBase(T item)
        {
            return InsertToTopItemBase(item);
        }

        /// <summary>
        /// Adds the specified item. base emthod for batch procressing
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected CollectionChangedEventArgs<T> InsertToTopItemBase(T item)
        {
            if (item == null) throw new ArgumentNullException("item");

            Items.Add(item);
            return new CollectionChangedEventArgs<T>(CollectionChangedAction.InsertToTop, item);
        }

        /// <summary>
        /// Removes the item. For butch processing. Not thread safe.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        CollectionChangedEventArgs<T> IObjectCollection<T>.RemoveItemBase(int index, T item)
        {
            return RemoveItemBase(index, item);
        }
        /// <summary>
        /// Removes the item. For butch processing. Not thread safe.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected CollectionChangedEventArgs<T> RemoveItemBase(int index, T item)
        {
            Items.RemoveAt(index);
            return new CollectionChangedEventArgs<T>(CollectionChangedAction.Remove, item);
        }

        /// <summary>
        /// Updates the item. For butch processing. Not thread safe.
        /// </summary>
        /// <param name="oldItem">The old item.</param>
        /// <param name="newItem">The new item.</param>
        /// <returns></returns>
        CollectionChangedEventArgs<T> IObjectCollection<T>.UpdateItemBase(T oldItem, T newItem)
        {
            return UpdateItemBase(oldItem, newItem);
        }
        /// <summary>
        /// Updates the item. For butch processing. Not thread safe.
        /// </summary>
        /// <param name="oldItem">The old item.</param>
        /// <param name="newItem">The new item.</param>
        /// <returns></returns>
        protected CollectionChangedEventArgs<T> UpdateItemBase(T oldItem, T newItem)
        {
            if (!ReferenceEquals(oldItem, newItem)) newItem.Copy(oldItem);
            return new CollectionChangedEventArgs<T>(CollectionChangedAction.Update, oldItem);
        }

        /// <summary>
        /// Finds the specified match.
        /// </summary>
        /// <param name="match">The match.</param>
        /// <param name="index">The index.</param>
        /// <returns>Index of first founded  intem</returns>
        T IObjectCollection<T>.Find(Predicate<T> match, out int index)
        {
            return Find(match, out index);
        }

        /// <summary>
        /// Called when [collection changed].
        /// </summary>
        /// <param name="e">The <see cref="CollectionChangedEventArgs{T}"/> instance containing the event data.</param>
        void IObjectCollection<T>.OnCollectionChanged(CollectionChangedEventArgs<T> e)
        {
            OnCollectionChanged(e);
        }

        #endregion

        /// <summary>
        /// Removes the item. Thread safe.
        /// </summary>
        /// <param name="item">The item.</param>
        protected void RemoveItem(T item)
        {
            bool deleted;
            lock (SynchRoot)
            {
                deleted = Items.Remove(item);
            }
            if (deleted) OnCollectionChanged(new CollectionChangedEventArgs<T>(CollectionChangedAction.Remove, item));
        }

        /// <summary>
        /// Adds the item.
        /// </summary>
        /// <param name="item">The item.</param>
        protected void AddItem(T item)
        {
            lock (SynchRoot)
            {
                Items.Add(item);
            }
            OnCollectionChanged(new CollectionChangedEventArgs<T>(CollectionChangedAction.Add, item));
        }
        /// <summary>
        /// Updates the item from.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        protected void UpdateItemFrom(T newValue)
        {
            T oldItem;
            lock (SynchRoot)
            {
                int index;
                oldItem = Find(o => o.Id.Equals(newValue.Id), out index);
            }
            OnCollectionChanged(UpdateItemBase(oldItem, newValue));
        }

        /// <summary>
        /// Adds the range.
        /// </summary>
        /// <param name="items">The items.</param>
        protected void AddRange(IEnumerable<T> items)
        {
            if (items == null) return;
            lock (SynchRoot)
            {
                Items.AddRange(items);
            }
            OnCollectionChanged(CollectionChangedAction.Add, items.ToArray());
        }


        /// <summary>
        /// Raises the <see cref="CollectionChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="CollectionChangedEventArgs{T}"/> instance containing the event data.</param>
        protected virtual void OnCollectionChanged(CollectionChangedEventArgs<T> e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, e);
            }
        }

        /// <summary>
        /// Called when [collection reset].
        /// </summary>
        protected override void OnCollectionReset()
        {
            base.OnCollectionReset();
            OnCollectionChanged(new CollectionChangedEventArgs<T>());
        }

        /// <summary>
        /// Called when [collection changed].
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="item">The item.</param>
        protected void OnCollectionChanged(CollectionChangedAction action, T item)
        {
            OnCollectionChanged(new CollectionChangedEventArgs<T>(action, new[] { item }));
        }
        #endregion





        private void OnCollectionChanged(CollectionChangedAction action, T[] items)
        {
            OnCollectionChanged(new CollectionChangedEventArgs<T>(action, items));
        }

    }
}